package org.luxor.commons.workflow.component.cmd;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.parser.factory.ActivityBehaviorFactory;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.cmd.NeedsActiveTaskCmd;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.deploy.DeploymentManager;
import org.activiti.engine.impl.persistence.deploy.ProcessDefinitionCacheEntry;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.apache.xmlgraphics.util.DateFormatUtil;
import org.luxor.commons.workflow.component.cache.CustomDeploymentCache;
import org.luxor.commons.workflow.component.cache.DeploymentContext;
import org.luxor.commons.workflow.constant.TransientVariableConst;
import org.luxor.commons.workflow.constant.VariableConst;
import org.luxor.commons.workflow.utils.ProcessUtils;

import java.util.*;

/**
 * 普通任务-加(前)签
 *
 * @author Mr.yan  @date 2022/2/23
 */
public class AddBeforeTaskInstanceExecutionCmd extends NeedsActiveTaskCmd<Void> {
    private static final long serialVersionUID = 1L;

    private final String collectionElementIndexVariable = "loopCounter";
    private final String NUMBER_OF_INSTANCES = "nrOfInstances";
    private final String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";
    private final String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances";

    private Map<String, Object> variables;
    private Map<String, Object> transientVariables;
    private boolean localScope;

    public AddBeforeTaskInstanceExecutionCmd(String taskId) {
        this(taskId, null, null);
    }

    public AddBeforeTaskInstanceExecutionCmd(String taskId, Map<String, Object> variables) {
        this(taskId, variables, null);
    }

    public AddBeforeTaskInstanceExecutionCmd(String taskId, Map<String, Object> variables, Map<String, Object> transientVariables) {
        super(taskId);
        this.variables = variables == null ? new HashMap<String, Object>() : variables;
        this.transientVariables = transientVariables == null ? new HashMap<String, Object>() : transientVariables;
    }

    public AddBeforeTaskInstanceExecutionCmd(String taskId, Map<String, Object> variables, boolean localScope) {
        super(taskId);
        this.variables = variables == null ? new HashMap<String, Object>() : variables;
        this.localScope = localScope;
    }

    @Override
    public Void execute(CommandContext commandContext, TaskEntity taskEntity) {
        DeploymentContext.setProcInstId(taskEntity.getProcessInstanceId());

        boolean isMultiInstanceActivity = false;
        // 获取缓存中的流程实例定义
        ProcessDefinitionCacheEntry processDefinitionCacheEntry = getProcessDefinitionCacheEntry(commandContext, taskEntity.getProcessDefinitionId());
        // 获取当前活动执行实例
        ExecutionEntity miExecution = taskEntity.getExecution();
        UserTask activity = (UserTask) processDefinitionCacheEntry.getProcess().getFlowElement(taskEntity.getTaskDefinitionKey());
        if (!(activity instanceof UserTask)) {
            throw new ActivitiException("普通任务加签功能，暂不支持非用户活动类型");
        }
        if (activity.getBehavior() instanceof MultiInstanceActivityBehavior) {
            isMultiInstanceActivity = true;
            miExecution = getMultiInstanceRootExecution(taskEntity.getExecution());
        }
        if (miExecution == null) {
            throw new ActivitiObjectNotFoundException("找不到流程执行实例");
        }

        // 清除多实例会签参数和运行实例
        if (isMultiInstanceActivity) {
            clearVariables(commandContext, miExecution);
        }

        // 添加前签
        addBeforeTaskInstanceExecution(commandContext, processDefinitionCacheEntry, miExecution, activity);
        return null;
    }

    /**
     * 加前签
     *
     * @param commandContext
     * @param processDefinitionCacheEntry
     * @param execution                   当前执行实例
     * @param activity                    当前节点
     * @return void
     */
    private void addBeforeTaskInstanceExecution(CommandContext commandContext, ProcessDefinitionCacheEntry processDefinitionCacheEntry,
                                                ExecutionEntity execution, UserTask activity) {
        Process process = processDefinitionCacheEntry.getProcess();
        // 创建(新节点)输出连接线
        SequenceFlow cloneSequenceFlow = activity.getIncomingFlows().get(0).clone();
        cloneSequenceFlow.setId(ProcessUtils.createFlowId());
        cloneSequenceFlow.setSourceFlowElement(null);
        cloneSequenceFlow.setSourceRef(null);
        cloneSequenceFlow.setTargetFlowElement(process.getFlowElement(execution.getActivityId()));
        cloneSequenceFlow.setTargetRef(execution.getActivityId());

        // 新建前置UserTask活动节点
        UserTask cloneUserTask = activity.clone();
        cloneUserTask.setId(ProcessUtils.createActivityId());
        cloneUserTask.setName(cloneUserTask.getName() == null ? "前置任务" : cloneUserTask.getName() + "(前置任务)");
        Object assignee = transientVariables.get(TransientVariableConst.ASSIGNEE);
        if (assignee != null) {
            cloneUserTask.setAssignee((String) assignee);
        }
        Object candidateUserList = transientVariables.get(TransientVariableConst.CANDIDATE_USER_LIST);
        if (assignee != null) {
            cloneUserTask.setCandidateUsers((List<String>) candidateUserList);
        }
        Object candidateGroupList = transientVariables.get(TransientVariableConst.CANDIDATE_GROUP_LIST);
        if (assignee != null) {
            cloneUserTask.setCandidateGroups((List<String>) candidateGroupList);
        }
        Object dueDate = transientVariables.get(TransientVariableConst.DUE_DATE);
        if (dueDate != null) {
            cloneUserTask.setDueDate(DateFormatUtil.formatISO8601((Date) dueDate, TimeZone.getDefault()));
        }
        Object priority = transientVariables.get(TransientVariableConst.PRIORITY);
        if (priority != null) {
            cloneUserTask.setPriority(JsonNodeFactory.instance.pojoNode(priority).asText());
        }
        cloneUserTask.setIncomingFlows(Arrays.asList());
        cloneUserTask.setOutgoingFlows(Arrays.asList(cloneSequenceFlow));
        cloneUserTask.setBehavior(createUserTaskActivityBehavior(cloneUserTask));

        // 将新增的任务和连接线添加到当前流程实例对象
        process.addFlowElement(cloneUserTask);
        process.addFlowElement(cloneSequenceFlow);

        // 刷新流程实例定义缓存数据
        processDefinitionCacheEntry.setProcess(process);
        refreshRuProcessDefinitionCacheEntry(commandContext, execution.getProcessDefinitionId(), processDefinitionCacheEntry);

        // 触发流程运转
        if (activity.getBehavior() instanceof MultiInstanceActivityBehavior) {
            commandContext.getExecutionEntityManager().deleteChildExecutions(execution, "jump to:" + cloneUserTask.getId(), false);
            execution.setActive(true);
            execution.setMultiInstanceRoot(false);
            commandContext.getExecutionEntityManager().update(execution);
            // 设置参数变量
            setVariables(execution);
            // 触发实例运转
            execution.setCurrentFlowElement(cloneUserTask);
            commandContext.getAgenda().planContinueMultiInstanceOperation(execution);
        } else {
            commandContext.getTaskEntityManager().delete(taskId);
            commandContext.getIdentityLinkEntityManager().deleteIdentityLinksByTaskId(taskId);
            commandContext.getHistoryManager().recordTaskEnd(taskId, "jump to:" + cloneUserTask.getId());
            commandContext.getHistoryManager().recordActivityEnd(execution, "jump to:" + cloneUserTask.getId());
            // 设置参数变量
            setVariables(execution);
            // 触发实例运转
            execution.setCurrentFlowElement(cloneUserTask);
            commandContext.getAgenda().planContinueProcessInCompensation(execution);
        }
        return;
    }

    private ProcessDefinitionCacheEntry getProcessDefinitionCacheEntry(CommandContext commandContext, String processDefinitionId) {
        DeploymentManager deploymentManager = commandContext.getProcessEngineConfiguration().getDeploymentManager();
        return deploymentManager.getProcessDefinitionCache().get(processDefinitionId);
    }

    private void refreshRuProcessDefinitionCacheEntry(CommandContext commandContext, String processDefinitionId, ProcessDefinitionCacheEntry cacheEntry) {
        DeploymentManager deploymentManager = commandContext.getProcessEngineConfiguration().getDeploymentManager();
        CustomDeploymentCache processDefinitionCache = (CustomDeploymentCache) deploymentManager.getProcessDefinitionCache();
        processDefinitionCache.refreshRuProcessDefinition(processDefinitionId, cacheEntry);
    }

    private UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
        ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) ProcessEngines.getDefaultProcessEngine().getProcessEngineConfiguration();
        ActivityBehaviorFactory activityBehaviorFactory = processEngineConfiguration.getActivityBehaviorFactory();
        return activityBehaviorFactory.createUserTaskActivityBehavior(userTask);
    }

    private void setVariables(ExecutionEntity execution) {
        if (variables != null) {
            if (localScope) {
                execution.setVariablesLocal(variables);
            } else {
                execution.setVariables(variables);
            }
        }
        if (transientVariables != null) {
            if (localScope) {
                execution.setTransientVariablesLocal(transientVariables);
            } else {
                execution.setTransientVariables(transientVariables);
            }
        }
    }

    private void clearVariables(CommandContext commandContext, ExecutionEntity execution) {
        Integer nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES);
        setLoopVariable(execution, NUMBER_OF_INSTANCES, nrOfInstances);
        setLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES, nrOfInstances);
        setLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES, 0);
        removeLocalLoopVariable(execution, collectionElementIndexVariable);
        if (execution.getParent() != null) {
            ArrayNode arrayNodes = (ArrayNode) execution.getVariable(VariableConst.ASSIGNEE_LIST);
            List<String> assigneeList = new ArrayList<>();
            for (JsonNode node : arrayNodes) {
                assigneeList.add(node.asText());
            }
            setLoopVariable(execution.getParent(), VariableConst.ASSIGNEE_LIST, assigneeList);
        }
        commandContext.getExecutionEntityManager().deleteExecutionAndRelatedData(execution, "delete current multi instance", false);
    }

    private ExecutionEntity getMultiInstanceRootExecution(ExecutionEntity executionEntity) {
        ExecutionEntity multiInstanceRootExecution = null;
        ExecutionEntity currentExecution = executionEntity;
        while (currentExecution != null && multiInstanceRootExecution == null && currentExecution.getParent() != null) {
            if (currentExecution.isMultiInstanceRoot()) {
                multiInstanceRootExecution = currentExecution;
            } else {
                currentExecution = currentExecution.getParent();
            }
        }
        return multiInstanceRootExecution;
    }

    private Integer getLoopVariable(DelegateExecution execution, String variableName) {
        Object value = execution.getVariableLocal(variableName);
        return value == null ? 0 : (Integer) value;
    }

    private void setLoopVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setVariableLocal(variableName, value);
    }

    private void removeLocalLoopVariable(DelegateExecution execution, String variableName) {
        execution.removeVariableLocal(variableName);
    }

}
